%% @author jovial

-module(terreno).


-export([iniciar/0, obterPosicoesValidas/1, loop/1]).
-record(dadosTerreno, {terreno, presas, predadores, posicoes}).

%% ====================================================================
%% API functions
%% ====================================================================

iniciar() ->
    register(terreno, spawn(terreno, loop, [#dadosTerreno{}])).

%% ====================================================================
%% Internal functions
%% ====================================================================
 
loop(THIS) ->
	receive
		{reiniciar, Criaturas} ->
			Novo_THIS = criarInformacoesTerreno(Criaturas);
		
		{moverCriatura, ID_Criatura, Direcao} ->
			CriaturaViva = criaturaViva(THIS, ID_Criatura),
			if CriaturaViva ->
				{CA_THIS, CriaturaAtualizada} = atualizarCiclo(THIS, ID_Criatura),
				PodeMover = criatura:podeSeMover(CriaturaAtualizada),
				if PodeMover ->
			    	Novo_THIS = moverCriatura(CA_THIS, CriaturaAtualizada, Direcao);
				   true ->
					   simulador ! {atualizarCriatura, CriaturaAtualizada},
					   Novo_THIS = CA_THIS
				end;
			true ->
				Novo_THIS = THIS
			end;
		
		{obterPosicoesPresas, PID, ID_Criatura} ->
			CriaturaViva = criaturaViva(THIS, ID_Criatura),
			if CriaturaViva ->
				[Posicao] = dict:fetch(ID_Criatura, THIS#dadosTerreno.posicoes),
				PID ! {listaPosicoesPresas, Posicao, getPosicoesCriaturas(THIS, presa)};
			true ->
				PID ! {listaPosicoesPresas, {0,0},[]}
			end,
			Novo_THIS = THIS;
		
		{obterPosicoesPredadores, PID, ID_Criatura} ->
			CriaturaViva = criaturaViva(THIS, ID_Criatura),
			if CriaturaViva ->
			   [Posicao] = dict:fetch(ID_Criatura, THIS#dadosTerreno.posicoes),
				PID ! {listaPosicoesPredadores, Posicao, getPosicoesCriaturas(THIS, predador)};
			true ->
				PID ! {listaPosicoesPredadores, {0,0}, []}
			end,
			Novo_THIS = THIS;
		
		{obterInformacoesCriaturas, PID} ->
			PID ! {informacoesCriaturas, criarMapaCriaturaPosicao(THIS)},
			Novo_THIS = THIS;

		_ ->
			Novo_THIS = THIS
	end,
	loop(Novo_THIS).

%% ====================================================================
%% Mover criatura
%% ====================================================================

atualizarCiclo(THIS, ID_Criatura) ->
	Criatura = getCriatura(THIS, ID_Criatura),
	[Posicao] = dict:fetch(ID_Criatura, THIS#dadosTerreno.posicoes),
		
	SC_THIS = removerCriatura(THIS, Criatura),
	CriaturaAtualizada = criatura:novoCiclo(Criatura),
	THIS_Atualizado = adicionarCriatura(SC_THIS, CriaturaAtualizada, Posicao),
	{THIS_Atualizado, CriaturaAtualizada}.
	

moverCriatura(THIS, Criatura, Direcao) -> 
	ID_Criatura = criatura:id(Criatura),
	
	[Posicao] = dict:fetch(ID_Criatura, THIS#dadosTerreno.posicoes),
	PosicoesPossiveis = direcao:obterPosicoesPossiveis(Direcao, Posicao),
	PosicaoSelecionada = selecionarPosicaoDestino(THIS#dadosTerreno.terreno, PosicoesPossiveis),
	
	if PosicaoSelecionada =/= undefined ->
		THIS_SemCriatura = removerCriatura(THIS, Criatura),
		THIS_PosMovimento = adicionarCriatura(THIS_SemCriatura, Criatura, PosicaoSelecionada),
		
		simulador ! {criaturaMoveu, Criatura, Posicao, PosicaoSelecionada},
		
		analisarNascimentosMortes(THIS_PosMovimento, Criatura, PosicaoSelecionada);
	true ->
		simulador ! {atualizarCriatura, Criatura},
		analisarNascimentosMortes(THIS, Criatura, Posicao)
	end.

selecionarPosicaoDestino(Terreno, [{X, Y}|T]) when X >= 0, X < 100, Y >= 0, Y < 100 ->
	Criatura = array:get(Y, array:get(X, Terreno)),
	if Criatura =/= undefined ->
		selecionarPosicaoDestino(Terreno, T);
	true ->
		{X, Y}
	end;
selecionarPosicaoDestino(Terreno, [_|T]) ->
	selecionarPosicaoDestino(Terreno, T);
selecionarPosicaoDestino(_, []) ->
	undefined.

analisarNascimentosMortes(THIS, Criatura, Posicao) -> 
	Terreno = THIS#dadosTerreno.terreno,
	MapaPresas = THIS#dadosTerreno.presas,
	MapaPredadores = THIS#dadosTerreno.predadores,
	
	ResultadoMortes = controle_vida:analisarMortes(MapaPresas, MapaPredadores, Terreno, Criatura, Posicao),
	
	NovoTHIS = atualizarCriaturas(THIS, ResultadoMortes),
	
	CriaturaViva = criaturaViva(NovoTHIS, criatura:id(Criatura)),
	if CriaturaViva ->
		   	ResultadoNascimentos = controle_vida:analisarNascimentos(MapaPresas, MapaPredadores, Terreno, Criatura, Posicao),
			atualizarCriaturas(NovoTHIS, ResultadoNascimentos);
		true ->
			NovoTHIS
	end.

atualizarCriaturas(THIS, [{Evento, Criatura, PosicaoNascimento}|T]) ->
	ID_Criatura = criatura:id(Criatura),
	CriaturaViva = criaturaViva(THIS, ID_Criatura),
	if CriaturaViva or (Evento =:= nasceu) ->
		if Evento =:= atualizar ->
 			   [{X, Y}] = dict:fetch(ID_Criatura, THIS#dadosTerreno.posicoes),
			   Rem_THIS = removerCriatura(THIS, Criatura),
			   Novo_THIS = adicionarCriatura(Rem_THIS, Criatura, {X, Y});
		   Evento =:= nasceu ->
			   PosicaoLivre = obterPosicaoLivre(THIS#dadosTerreno.terreno,  PosicaoNascimento),
			   if PosicaoLivre =/= undefined ->
			   		Novo_THIS = adicionarCriatura(THIS, Criatura, PosicaoLivre),
		   	   		simulador ! {criaturaNasceu, Criatura, PosicaoLivre};
				  true ->
					  Novo_THIS = THIS
			   end;
		   true ->
			   [{X, Y}] = dict:fetch(ID_Criatura, THIS#dadosTerreno.posicoes),
			   Novo_THIS = removerCriatura(THIS, Criatura),
			   simulador ! {criaturaMorreu, Criatura, {X, Y}}
		end;
	   true ->
		   Novo_THIS = THIS
	end,
	atualizarCriaturas(Novo_THIS, T);
atualizarCriaturas(THIS, []) ->
	THIS.
	
%% ====================================================================
%% Obter posicoes relativas
%% ====================================================================

getPosicoesCriaturas(#dadosTerreno{presas = MapaCriaturas, posicoes = MapaPosicoes}, Tipo) when Tipo =:= presa ->
	getPosicoesCriaturas(MapaPosicoes, dict:to_list(MapaCriaturas), []);
getPosicoesCriaturas(#dadosTerreno{predadores = MapaCriaturas, posicoes = MapaPosicoes}, Tipo) when Tipo =:= predador ->
	getPosicoesCriaturas(MapaPosicoes, dict:to_list(MapaCriaturas), []).

getPosicoesCriaturas(MapaPosicoes, [{ID_Criatura, [_]}|T], Posicoes) ->
	[Posicao] = dict:fetch(ID_Criatura, MapaPosicoes),
	getPosicoesCriaturas(MapaPosicoes, T, [Posicao|Posicoes]);
getPosicoesCriaturas(_, [], Posicoes) ->
	Posicoes.

obterPosicaoLivre(Terreno, PosicaoRef) when is_tuple(PosicaoRef) ->
	PosicoesValidas = obterPosicoesValidas(PosicaoRef),
	obterPosicaoLivre(Terreno, PosicoesValidas);
obterPosicaoLivre(Terreno, [{X, Y}|T]) ->
	Criatura = array:get(Y, array:get(X, Terreno)),
	if Criatura =:= undefined ->
		   {X, Y};
	   true ->
		   obterPosicaoLivre(Terreno, T)
	end;
obterPosicaoLivre(_, []) ->
	undefined.

obterPosicoesValidas({Xref, Yref}) ->
	[{Xref + X, Yref + Y} || X <- lists:seq(-1, 1), Y <- lists:seq(-1, 1), X /= 0 orelse Y /= 0, Xref + X >= 0, Xref + X < 100, Yref + Y >= 0, Yref + Y < 100].

%% ====================================================================
%% Outras
%% ====================================================================

criaturaViva(#dadosTerreno{presas = Presas, predadores = Predadores}, ID_Criatura) ->
	{_, Tipo} = ID_Criatura,
	if Tipo =:= presa ->
		  dict:is_key(ID_Criatura, Presas);
	   true -> 
		  dict:is_key(ID_Criatura, Predadores)
	end.

removerCriatura(THIS, Criatura) -> 
	Terreno = THIS#dadosTerreno.terreno,
	ID_Criatura = criatura:id(Criatura),
	
	[{X, Y}] = dict:fetch(ID_Criatura, THIS#dadosTerreno.posicoes),
	
	{NovoMapaPresas, NovoMapaPredadores} = removerCriaturaMapa(THIS, ID_Criatura),
	NovoMapaPosicoes = dict:erase(ID_Criatura, THIS#dadosTerreno.posicoes),
	NovoTerreno = array:set(X, array:set(Y, undefined, array:get(X, Terreno)), Terreno),

	THIS#dadosTerreno{terreno = NovoTerreno, presas = NovoMapaPresas, predadores = NovoMapaPredadores, posicoes = NovoMapaPosicoes}.

adicionarCriatura(THIS, Criatura, {X, Y}) ->
	Terreno = THIS#dadosTerreno.terreno,
	ID_Criatura = criatura:id(Criatura),
	
	{NovoMapaPresas, NovoMapaPredadores} = adicionarCriaturaMapa(THIS, ID_Criatura, Criatura),
	NovoMapaPosicoes = dict:append(ID_Criatura,  {X, Y}, THIS#dadosTerreno.posicoes),
	NovoTerreno = array:set(X, array:set(Y, ID_Criatura, array:get(X, Terreno)), Terreno),

	THIS#dadosTerreno{terreno = NovoTerreno, presas = NovoMapaPresas, predadores = NovoMapaPredadores, posicoes = NovoMapaPosicoes}.


criarMapaCriaturaPosicao(#dadosTerreno{presas = MapaPresas, predadores = MapaPredadores, posicoes = MapaPosicoes}) ->
	criarMapaCriaturaPosicao(dict:to_list(MapaPresas) ++ dict:to_list(MapaPredadores), MapaPosicoes, []).
criarMapaCriaturaPosicao([{ID_Criatura, [Criatura]}|T], MapaPosicoes, MapaCriaturaPosicao) ->
	[Posicao] = dict:fetch(ID_Criatura, MapaPosicoes),
	NovaCriatura = {Criatura, Posicao},
	criarMapaCriaturaPosicao(T, MapaPosicoes, [NovaCriatura|MapaCriaturaPosicao]);
criarMapaCriaturaPosicao([], _, MapaCriaturaPosicao) ->
	MapaCriaturaPosicao.


%% ====================================================================
%% Inicializacao
%% ====================================================================

criarInformacoesTerreno(Criaturas) ->
	{MapaPresas, MapaPredadores} = construirMapaCriaturas(Criaturas),
	MapPosicoes = construirMapaPosicoes(MapaPresas, MapaPredadores),
	#dadosTerreno{presas = MapaPresas, predadores = MapaPredadores, posicoes = MapPosicoes, terreno = posicionarCriaturas(MapPosicoes)}.

construirMapaCriaturas(Criaturas) ->
	MapaPresas = dict:new(),
	MapaPredadores = dict:new(),
	construirMapaCriaturas(Criaturas, MapaPresas, MapaPredadores).
construirMapaCriaturas([H|T], MapaPresas, MapaPredadores) when H =:= presa ->
	Criatura = criatura:new(H),
	MapaPresasAtualizado = dict:append(criatura:id(Criatura), Criatura, MapaPresas),
	construirMapaCriaturas(T, MapaPresasAtualizado, MapaPredadores);
construirMapaCriaturas([H|T], MapaPresas, MapaPredadores) when H =:= predador ->
	Criatura = criatura:new(H),
	MapaPredadoresAtualizado = dict:append(criatura:id(Criatura), Criatura, MapaPredadores),
	construirMapaCriaturas(T, MapaPresas, MapaPredadoresAtualizado);
construirMapaCriaturas([], MapaPresas, MapaPredadores) ->
	{MapaPresas, MapaPredadores}.


construirMapaPosicoes(MapaPresas, MapaPredadores) -> 
	construirMapaPosicoes(dict:to_list(MapaPresas) ++ dict:to_list(MapaPredadores), posicoesPossiveis(), dict:new()).

construirMapaPosicoes([{ID_Criatura, _}|T], Posicoes, MapaPosicoes) ->
	Lenght = length(Posicoes),
	Div = random:uniform(Lenght),
	Posicao = lists:nth(Div, Posicoes),
	NovaLista = lists:sublist(Posicoes, 1, Div - 1) ++ lists:sublist(Posicoes, Div + 1, Lenght - Div),
	construirMapaPosicoes(T, NovaLista, dict:append(ID_Criatura, Posicao, MapaPosicoes));
construirMapaPosicoes([], _, MapaPosicoes) ->
	MapaPosicoes;
construirMapaPosicoes(_, [], MapaPosicoes) ->
	MapaPosicoes.


posicoesPossiveis() ->
	[{X, Y} || X <- lists:seq(0, 99), Y <- lists:seq(0, 99)].

posicionarCriaturas(MapPosicoes) ->
	Terreno = array:new([{size,100}, {default,array:new(100)}]),
	posicionarCriaturas(Terreno, dict:to_list(MapPosicoes)).
posicionarCriaturas(Terreno, [{ID_Criatura, [Posicao]}|T]) -> 
	NovoTerreno = setCriaturaPosicao(Terreno, ID_Criatura, Posicao),
	posicionarCriaturas(NovoTerreno, T);
posicionarCriaturas(Terreno, []) -> 
	Terreno.

setCriaturaPosicao(Terreno, ID_Criatura, {X, Y}) ->
	array:set(X, array:set(Y, ID_Criatura, array:get(X, Terreno)), Terreno).

getCriatura(THIS, ID_Criatura) ->
	{_, Tipo} = ID_Criatura,
	if Tipo =:= presa ->
		  [Presa] = dict:fetch(ID_Criatura, THIS#dadosTerreno.presas),
		  Presa;
	   true ->
		  [Predador] = dict:fetch(ID_Criatura, THIS#dadosTerreno.predadores),
		  Predador
	end.

removerCriaturaMapa(THIS, ID_Criatura) ->
	{_, Tipo} = ID_Criatura,
	if Tipo =:= presa ->
			NovoMapaPresas = dict:erase(ID_Criatura, THIS#dadosTerreno.presas),
			{NovoMapaPresas, THIS#dadosTerreno.predadores};
	   true ->
			NovoMapaPredadores = dict:erase(ID_Criatura, THIS#dadosTerreno.predadores),
			{THIS#dadosTerreno.presas, NovoMapaPredadores}
	end.
	
adicionarCriaturaMapa(THIS, ID_Criatura, Criatura) ->
	{_, Tipo} = ID_Criatura,
	if Tipo =:= presa ->
			NovoMapaPresas = dict:append(ID_Criatura, Criatura, THIS#dadosTerreno.presas),
			{NovoMapaPresas, THIS#dadosTerreno.predadores};
	   true ->
			NovoMapaPredadores = dict:append(ID_Criatura, Criatura, THIS#dadosTerreno.predadores),
			{THIS#dadosTerreno.presas, NovoMapaPredadores}
	end.